// Main driver for Mission
// Copyright 2005 (C) Ralph Thomas

#include <model/model.h>
#include <model/proxy/proxy.h>
#include <model/attributeNames.h>
#include <model/attributeInfo.h>
#include <view/factory.h>
#include <toolkit/application.h>
#include <util/resources.h>
#include <util/readFile.h>
#include <datapumps/database.h>

#include <ui/function_adapter.h>

#include <boost/bind.hpp>
#include <boost/filesystem/path.hpp>

#include <client_assembler.hpp>
#include <factory.hpp>
#include <adobe/config.hpp>
#include <adobe/name.hpp>
#include <adobe/value.hpp>
#include <adobe/dictionary.hpp>
#include <adobe/array.hpp>
#include <adobe/adam.hpp>
#include <adobe/adam_evaluate.hpp>
#include <adobe/adam_parser.hpp>
#include <adobe/istream.hpp>
#include <adobe/xstring.hpp>
#include <adobe/future/file_slurp.hpp>

#include <fstream>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <stdexcept>
#include <map>
// for getenv
#include <stdlib.h>

// XXX: testing
#include <gtk/gtk.h>

#ifdef ADOBE_PLATFORM_WIN
#include <windows.h>
#include <shlobj.h>
#include <objbase.h>
#endif
#ifdef ADOBE_PLATFORM_MAC
#include "carbon_safe.hpp"

//
/// Convert a MacOS FSRef into a Boost path. This allows us to use the
/// boost::filesystem library to perform operations on the path, rather
/// than using the FSRef API.
///
/// \param	location	the FSRef to make into a bfs::path
/// \return	a bfs path of the given location.
//
boost::filesystem::path fsref_to_path( const FSRef& location )
{
	std::size_t			max_size (1024);
	std::vector<char>	path_buffer(max_size);
	OSStatus			error(noErr);
	
	while (true)
	{
		error = ::FSRefMakePath(&location, reinterpret_cast<UInt8*>(&path_buffer[0]), max_size);
		if (error != pathTooLongErr) break;
		
		max_size *= 2;
		path_buffer.resize(max_size);
	}
	
	ADOBE_REQUIRE_STATUS(error);
	
	return boost::filesystem::path( &path_buffer[0], boost::filesystem::native );
}

#endif

namespace bfs = boost::filesystem;

void open_url( const adobe::dictionary_t& dict );
//
/// Function factory.
//
adobe::value_t dict_function_factory( adobe::sheet_t& sheet, adobe::name_t name, const adobe::dictionary_t& dict ) {
	if( name == adobe::static_name_t( "open_url" ) )
		return adobe::value_t( app::function_adapter_t::create( &open_url, sheet, dict ) );
	if( name == adobe::static_name_t( "db_query" ) )
		return datapumps::make_db_query_datapump( util::getMissionPath(), dict, sheet );
	if( name == adobe::static_name_t( "db_row" ) )
		return datapumps::make_db_row_datapump( util::getMissionPath(), dict, sheet );
	if( name == adobe::static_name_t( "db_value" ) )
		return datapumps::make_db_value_datapump( util::getMissionPath(), dict, sheet );
	throw std::runtime_error( "Unknown function" );
}

static int _count = 0;
//
/// This controller class loads up a sheet and the GUI. The rest should be
/// handled by ASL.
//
class controller {
	adobe::sheet_t* _sheet;
	eve_client::window_server_t* _ws;
	std::string _url;
	std::string _sheetString;
	std::string _viewString;
	bool _loadFromModel;
	//
	/// This class is used to extract information from the sheet.
	//
	class extractor {
		std::string* _value;
	  public:
		extractor( std::string* v ) : _value( v ) {}
		extractor( const extractor& rhs ) : _value( rhs._value ) {}
		void operator()( const adobe::value_t& v ) {
			std::string tmp;
			v.get( tmp );
			*_value = tmp;
		}
	//	const std::string& get() { return *_value; }
	};
  public:
	controller( const std::string& url )
		: _sheet( 0 ), _ws( 0 ), _url( url ), _loadFromModel( true )
	{}
	controller( const std::string& url, const std::string& sheet,
		const std::string& view ) : _sheet( 0 ), _ws( 0 ), _url( url ),
		_sheetString( sheet ), _viewString( view ),
		_loadFromModel( false ) {}
	~controller() {
		if( _ws ) delete _ws;
		if( _sheet ) delete _sheet;
	}
	bool run() {
		//
		// Extract the sheet and view to use from the model.
		//
		model::model* mod = model::model::fromString( _url );
		if( !mod ) return false;
		try {
			//
			// We can either load the sheet from the model
			// or create it ourselves from a given serialized
			// sheet.
			//
			if( _loadFromModel )
				_sheet = mod->getAttribute( model::kSheet ).get<adobe::sheet_t*>();
			else {
				_sheet = new adobe::sheet_t();
				std::stringstream iss( _sheetString );
				adobe::parse( iss, adobe::line_position_t(
					"<inline sheet>" ),
					adobe::bind_to_sheet( *_sheet ) );
				//
				// Set the model into the sheet. This is
				// the standard way of doing it (should be
				// moved into util?).
				//
				adobe::name_t model_url( "model_url" );
				_sheet->update();
				_sheet->touch( &model_url, (&model_url)+1 );
				_sheet->set( model_url, adobe::value_t( _url ) );
				_sheet->update();
			}
			if( !_sheet ) return false;
			//
			// We get to create the view.
			//
			if( _loadFromModel ) 
				_viewString = mod->getAttribute( model::kView ).get<std::string>();
			//
			// Create a window server to use.
			//
			_ws = new eve_client::window_server_t( "", *_sheet );
			//
			// Create a factory instance that can take care of the complicated
			// things we need to make.
			//
			view::factory my_factory;
			//
			// Use our own factory.
			//
			_ws->set_widget_factory(
				boost::bind( &view::factory::build, &my_factory, _1, _2, _3, _4 ),
				boost::bind( &view::factory::pop, &my_factory ) );
			_ws->set_dictionary_function_factory( &dict_function_factory );
			//
			// Get told about actions going on in the GUI.
			//
			_ws->set_action_fallback( boost::bind(
				&controller::action, this, _1, _2 ) );
			//
			// Build the GUI.
			//
			std::stringstream viewStream( _viewString );
			_ws->push_back( viewStream,
				adobe::line_position_t::ident_pair_t( adobe::name_t("modelstream"), 0 ),
				size_normal_s );
			mod->release();
			//
			// Increment the count so that we only quit when all the
			// windows are closed.
			//
			_count++;
		} catch( std::exception& ex ) {
			std::cerr << "FATAL: " << ex.what() << std::endl;
			return false;
		} catch( ... ) { return false; }
		return true;
	}
	//
	// void action( const adobe::name_t& name,
	//	const adobe::value_t& value )
	//
	/// This function is called when an action happens in the GUI.
	///
	/// \param	name	the name of the action called.
	/// \param	value	the value of the action.
	//
	void action( const adobe::name_t& name, const adobe::value_t& value ) {
		if( name == adobe::static_name_t( "cancel" ) ) {
			//
			// Cancel was pressed, we quit when we get to zero
			// open windows.
			//
			_count--;
			if( _count == 0 )
				toolkit::application::getInstance().quit();
			delete this;
			return;
		} else if( name == adobe::static_name_t( "open" ) ) {
			std::string url;
			if( value.get( url ) ) {
				//
				// Apparently we should open something. Instead of doing
				// that we can just set "model_url" in the sheet.
				//
				(new controller( url ))->run();
			}
		} else {
			//
			// There's a chance that this is a model action, if
			// we get a URL and an argument.
			//
			adobe::array_t parameters;
			if( !value.get( parameters ) ) return;
			if( parameters.size() != 2 ) return;
			//
			// Try to get the URL out of the parameters.
			//
			adobe::name_t cellname;
			if( !parameters.at( 0 ).get( cellname ) ) return;
			//
			// Now we need to get this cell out of the sheet, this
			// is a bit of a pain, because there's no direct
			// getter API.
			//
			std::string url;
			extractor e( &url );
			adobe::sheet_t::connection_t connection =
				_sheet->monitor_value( cellname, e );
			_sheet->touch( &cellname, (&cellname)+1 );
			_sheet->update();
			connection.disconnect();
			if( url.empty() ) return;
			//
			// Create some model from that URL, return if we can't.
			//
			model::model* myMod = model::model::fromString( url );
			if( !myMod ) return;
			myMod->execute( name.get(), parameters.at( 1 ) );
			myMod->release();
		}
	}
};
//
/// Open a URL in the given dictionary in a new window.
//
void open_url( const adobe::dictionary_t& dict ) {
	if( !dict.count( adobe::static_name_t( "url" ) ) ) throw std::runtime_error( "open_url: no url given to dictionary" );
	std::string url = dict.get<std::string>( adobe::static_name_t( "url" ) );
	(new controller( url ))->run();
}

enum appType {
	kFileManager = 0,
	kPhotoManager = 1
};

#ifdef _WINDOWS
int WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int ) {
    CoInitialize( 0 );
#else
int main( int argc, char** argv ) {
#endif
	gtk_init(&argc, &argv);
	//
	// By default we're a file manager.
	//
	appType app_type( kFileManager );
	std::string url( "file://" );
#ifdef _WINDOWS
	//
	// Are we a photo manager?
	//
	std::string cmdLine( GetCommandLine() );
	if( cmdLine.find( "mission-photo" ) != std::string::npos )
		app_type = kPhotoManager;
	//
	// On Windows start looking at the user's "My Documents" folder.
	//
	TCHAR szPath[MAX_PATH];
	if(SUCCEEDED(SHGetFolderPath(NULL, 
                             (app_type == kPhotoManager) ? CSIDL_MYPICTURES : CSIDL_PERSONAL, 
                             NULL, 
                             0, 
                             szPath)))
		url += szPath;
#else
	const char* home( getenv( "HOME" ) );
	if( home ) url += std::string( home );
	if( argc > 1 ) url = argv[1];
	bfs::path app( argv[0], bfs::native );
	if( app.leaf() == "mission-photo" ) {
		app_type = kPhotoManager;
		url += "/photos";
	}
#endif
#ifdef ADOBE_PLATFORM_MAC
	//
	// Load up the metrics for Macintosh. This is where all of the
	// widget dimensions come from (because the Apple API gets the
	// values wrong).
	//
	// XXX: This code is sloppy! Need better error handling.
	//
	ProcessSerialNumber	psn;
	FSRef				location;
	GetCurrentProcess( &psn );
	GetProcessBundleLocation( &psn, &location );
	bfs::path metricsPath( fsref_to_path( location ) /
		"Contents/Resources/metrics_glossary.xstr" );
	adobe::file_slurp<char> metrics( metricsPath );
	adobe::xstring_context_t	mac_metrics_context(metrics.begin(),
		metrics.end(), adobe::line_position_t( "metrics_glossary.xstr" ) );
	metrics.release();
#endif
	//
	// First initialize the application. This ensures that all extra
	// resources are loaded.
	//
	try {
		toolkit::application& the_app = toolkit::application::getInstance();
		if( the_app.init() != toolkit::application::kOk ) return 1;
		//
		// Now create and run the controller and app.
		//
		controller* c( 0 );
	       	if( app_type == kFileManager ) c = new controller( url );
		else c = new controller( url, util::readFile( util::getResource( "x-mission/x-photo-directory", "adam" ) ), util::readFile( util::getResource( "x-mission/x-photo-directory", "eve" ) ) );
		if( !c->run() ) return 1;
		the_app.run();
	} catch( std::exception& ex ) {
		std::cout << "FATAL EXCEPTION: " << ex.what() << std::endl;
		throw;
		return 1;
	} catch( ... ) {
		std::cout << "UNKNOWN FATAL EXCEPTION" << std::endl;
		return 1;
	}
	return 0;
}

//
// This is required for linking against Boost.Thread on Win32.
//
extern "C" void tss_cleanup_implemented() {}
